Skip to content

Conversation

@yangmingshan
Copy link

@yangmingshan yangmingshan commented Jan 4, 2025

Fix #992

I run into same issue when I porting Pinia to Vue Mini, and I found another debugger issue for sync subscription (please see the test).

I managed a way to fix them, the fix may have extra memory / performance costs, I didn't do any measurements. But I used it in Pinia for Vue Mini anyway.

Summary by CodeRabbit

  • Tests

    • Updated expectations for subscription synchronization and added a test covering debugger events when a subscription is not triggered by a patch.
  • Improvements

    • More consistent subscription triggering so events are delivered reliably after patches and direct synchronous mutations.

@netlify
Copy link

netlify bot commented Jan 4, 2025

Deploy Preview for pinia-official canceled.

Name Link
🔨 Latest commit 807cfd6
🔍 Latest deploy log https://app.netlify.com/projects/pinia-official/deploys/6913650e50eb9100086cd252

@netlify
Copy link

netlify bot commented Jan 4, 2025

Deploy Preview for pinia-playground ready!

Name Link
🔨 Latest commit 60b9c6d
🔍 Latest deploy log https://app.netlify.com/sites/pinia-playground/deploys/677912aa1a67c50008c55c37
😎 Deploy Preview https://deploy-preview-2870--pinia-playground.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link
Member

posva commented Jan 4, 2025

Thanks! I will take a look when I can

@posva posva changed the base branch from v2 to v3 February 12, 2025 08:51
@yangmingshan
Copy link
Author

This PR is now based on v3 and can be merged

Does v2 need this fix? If so, I can open another PR to fix v2

@posva
Copy link
Member

posva commented Feb 26, 2025

Thanks! I think it's fine to keep this for v3 only so don't worry about it

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jul 23, 2025

Open in StackBlitz

npm i https://pkg.pr.new/@pinia/nuxt@2870
npm i https://pkg.pr.new/pinia@2870
npm i https://pkg.pr.new/@pinia/testing@2870

commit: 807cfd6

() => {
shouldTrigger = isListening
},
{ deep: true, flush: 'sync' }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm revisitting this PR and realizing that this might impact perf too much

Copy link
Member

@posva posva Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to see in the playground the impact

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without checking the playground, I think we can't go this way: #610

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 2025

Walkthrough

Refactors store subscription internals to replace multi-flag listening with a single gating flag and two coordinated watchers, and updates tests (adjusted expectations and one new test) reflecting changed sync flush behavior and debugger event shape for non-patch subscriptions.

Changes

Cohort / File(s) Summary
Tests
packages/pinia/__tests__/subscriptions.spec.ts
Adjusted expectations for sync flush behavior (pre/post spies now expected to be called twice when a direct mutation follows a patch) and added one test asserting debuggerEvents is not an array when a subscription with flush: 'sync' is triggered by a direct mutation (non-patch).
Subscription Logic
packages/pinia/src/store.ts
Replaced multi-flag listening/nextTick transitions with a single shouldTrigger gate and direct isListening toggles; split a single watcher into two scoped watchers (stop1, stop2) to separate full-state vs direct/patch change detection; removed activeListener and nextTick-based throttling; updated HMR and patch dispatch to use the new gating and cleanup.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(173,216,230,0.15)
    participant User
    participant Store
    participant WatcherFull as Watcher (full state)
    participant WatcherDirect as Watcher (direct/patch)
    participant Subscribers
    end

    User->>Store: apply patch / HMR payload
    Store->>Store: isListening = false\nshouldTrigger = true
    Store->>WatcherFull: full-state watcher fires
    WatcherFull->>Store: checks shouldTrigger
    alt shouldTrigger true
        WatcherFull->>Subscribers: notify (patch events)
    end
    Store->>Store: isListening = true

    User->>Store: perform direct mutation
    Store->>Store: isListening = false\nshouldTrigger = true
    Store->>WatcherDirect: direct-change watcher fires
    WatcherDirect->>Store: checks shouldTrigger
    alt shouldTrigger true
        WatcherDirect->>Subscribers: notify (direct-change event)
    end
    Store->>Store: isListening = true
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review coordination between the two watchers and combined cleanup logic.
  • Verify HMR and patch paths correctly set/clear shouldTrigger and isListening.
  • Check the updated tests for correct expectations around sync flush ordering and debugger event shapes.

Poem

🐰 I hopped through lines of store and test,
Split watchers tending state's unrest.
One gate decides if listeners sing,
Patch then mutates — the bells now ring.
🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the core fix: preventing async subscriptions from running when state is mutated synchronously after a patch, which is precisely what the changeset implements.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bebea16 and 807cfd6.

📒 Files selected for processing (2)
  • packages/pinia/__tests__/subscriptions.spec.ts (1 hunks)
  • packages/pinia/src/store.ts (7 hunks)
🔇 Additional comments (7)
packages/pinia/__tests__/subscriptions.spec.ts (2)

372-374: LGTM - Test expectations correctly reflect the fix.

The updated expectations verify that pre and post flush subscriptions now correctly fire when state is synchronously mutated after a patch. The first call is from the patch (Line 367), and the second call is from the direct mutation (Line 368), which is the behavior this PR fixes.


376-388: LGTM - Good addition to verify debugger event shape.

This test correctly verifies that events is a single DebuggerEvent object (not an array) for direct mutations, distinguishing it from patch operations where events is an array.

packages/pinia/src/store.ts (5)

252-252: LGTM - Condition refinement prevents debugger event errors.

The updated condition correctly distinguishes between active listening (line 249) and patch operations where events should be collected in an array. The !store._hotUpdating guard prevents errors during HMR.


267-268: Variable initialization is correct.

The undefined initialization for both flags is appropriate. isListening is set to true at line 752, and shouldTrigger is always set by the sync watcher before being checked.


291-313: Patch gating mechanism is correct.

Setting isListening = false before mutations and true after correctly gates the subscription watchers, preventing double-firing while allowing the manual triggerSubscriptions call to work.


624-624: LGTM - Simplified HMR flow.

Removing the nextTick wrapper and setting isListening = true directly is consistent with the new synchronous gating approach and simplifies the HMR update flow.


437-467: Performance verification and documentation needed for two-watcher implementation.

The review comment's concerns are validated by issue #610, which documents that the deep: true + flush: 'sync' watcher combination creates measurable performance overhead. The two-watcher approach creates this tradeoff between correctness (fixing subscription behavior) and performance (increased watcher count per subscription).

The suggestions to measure real-world impact, document implications, and evaluate alternatives are appropriate given the known performance concerns in the project history. Proceed with the recommended verification steps.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/pinia/__tests__/subscriptions.spec.ts (1)

357-357: Fix grammatical error in test description.

The test name contains a grammatical error: "not be an array" should be "not an array".

Apply this diff:

-    it('debuggerEvents is not be an array when subscription is not trigger by patch', () => {
+    it('debuggerEvents is not an array when subscription is not triggered by patch', () => {
packages/pinia/src/store.ts (1)

267-268: Consider clarifying the shouldTrigger initialization.

While the comment states "The initial value does not matter", initializing shouldTrigger to undefined may cause confusion during debugging. Consider either:

  1. Initializing to false for clarity, or
  2. Expanding the comment to explain that it's set synchronously before being checked by async watchers

Apply this diff:

-  let shouldTrigger: boolean | undefined // The initial value does not matter, and no need to set to true at the end
+  // Captures the listening state synchronously for async watchers; set by sync watcher before async watchers check it
+  let shouldTrigger: boolean | undefined
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ea0440 and 92a5c0f.

📒 Files selected for processing (2)
  • packages/pinia/__tests__/subscriptions.spec.ts (1 hunks)
  • packages/pinia/src/store.ts (7 hunks)
🔇 Additional comments (5)
packages/pinia/__tests__/subscriptions.spec.ts (1)

371-385: Good test coverage for the fix.

This test effectively validates the core fix for issue #992, ensuring that subscriptions with all flush strategies ('sync', 'pre', 'post') are properly triggered when state is synchronously mutated after a patch operation.

packages/pinia/src/store.ts (4)

252-252: Condition refinement looks good.

The added !store._hotUpdating check appropriately prevents debugger event accumulation during hot module replacement operations.


291-291: isListening state management is correct.

The bookending of the patch operation with isListening = false/true properly prevents automatic watcher firing during the patch, while manual triggering (lines 315-319) ensures subscribers are notified once.

Also applies to: 313-313


624-624: HMR state transition is consistent.

The synchronous isListening = true assignment aligns with the removal of nextTick-based state transitions and maintains consistency with the $patch implementation.


437-467: Dual-watcher design appears intentional; performance concern is valid but unproven.

Analysis reveals this is a deliberate implementation to handle timing correctly: during state mutations, shouldTrigger captures the current isListening state synchronously via stop1, then stop2 fires the callback only if that captured state permits it. The design prevents subscriptions from firing at incorrect times due to race conditions with the isListening flag.

The performance overhead concern is valid—two watchers per subscription with deep watching on every mutation—but lacks empirical measurement. The codebase contains no performance benchmarks or complaints about subscription overhead. The trade-off appears accepted for correctness.

Before accepting or modifying this approach, measure actual performance impact in your usage patterns. If performance is acceptable, no changes needed. If degradation is significant, consider profiling to identify if this dual-watcher is the bottleneck or if other optimizations would suffice.

@codecov
Copy link

codecov bot commented Nov 3, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.90%. Comparing base (9182fdb) to head (807cfd6).

Additional details and impacted files
@@            Coverage Diff             @@
##               v3    #2870      +/-   ##
==========================================
- Coverage   90.90%   90.90%   -0.01%     
==========================================
  Files          19       19              
  Lines        1683     1682       -1     
  Branches      237      235       -2     
==========================================
- Hits         1530     1529       -1     
  Misses        151      151              
  Partials        2        2              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

posva added a commit that referenced this pull request Nov 3, 2025
Extracted from #2870
to fix #992
but can't be merged because the sync flush introduced a perf regression
@posva posva moved this from 🧑‍💻 In progress to 💬 In discussion in Pinia Roadmap Nov 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 💬 In discussion

Development

Successfully merging this pull request may close these issues.

$subscribe miss mutations with type of direct immediately afterpatch mutations

2 participants